// 原理： 为某个事件创建一个以及多个监听 并且依据 相应的指令 执行相关的事情

// 关键词解析
// 生活案例：例如 我点了一份外卖 我和外卖小哥说 ‘外卖到了放楼下并且给我打个电话’ 过了半个小时 ‘外卖小哥把我的外卖放置在了楼下并且给我打了个电话’  我接到电话之后 ‘去拿了外卖’

// 以上例子中 我是订阅者  外卖小哥是发布者（通知者） 而我去拿了外卖 是 发布者 对 订阅者 发出相应指令后 订阅者需要去做的事情


// on：订阅者，添加事件
// emit：发布者， 出发事件
// onOnce 订阅者，添加只能监听一次之后就失效的事件
// removeListener：删除单个订阅（事件）
// removeAllListener： 删除单个事件类型的订阅或删除全部订阅



// 事件管理
function Event() {

    // 最大订阅限制数量 每个发布者只能被 10个订阅者同时订阅 以防内存溢出
    this.defaultMaxEventListNum = 10;


    // 被订阅的发布者可能为一个或者多个 因此我们将被订阅的发布者全部存放在对象中

    // 例如：
    // event_list = {
    //     '发布者1': "一个订阅者",
    //     "发布者2": ["订阅者1号", "订阅者2号"]
    // }

    if (!this.event_list) {
        this.event_list = {};
    }
}

// 订阅
/**
 * 订阅参数
 * @param {*} eventName 被订阅的发布者的名称 
 * @param {*} cb 发布者发出指令后需要执行的方法或者事件
 */
Event.prototype.on = function (eventName, cb) {


    // 定义变量 存储当前被订阅者订阅的发布者
    let currentEvent = null;

    // 判断是否存在被订阅的发布者对象 不存在则创建空对象 存在则依据发布者名称 查找对应的订阅者
    if (!this.event_list) {
        // 创建空对象
        this.event_list = {};
    } else {
        // 查找对应的订阅者
        currentEvent = this.event_list[eventName];
    }

    // 判断当前被订阅的发布者 是否存在订阅者 不存在则将当前的订阅和发布者绑定
    if (!currentEvent) {
        currentEvent = this.event_list[eventName] = cb;
    }
    // 判断当前被订阅的发布者是否有且只有一个订阅者 将当前的订阅者继续绑定在发布者身上
    else if (isFunction(currentEvent)) {
        currentEvent = this.event_list[eventName] = [currentEvent, cb];
    }
    // 判断当前被订阅的发布者是否有多个订阅者 将当前的订阅者继续绑定在发布者身上
    else if (isArry(currentEvent)) {
        var len = currentEvent.length;
        // 判断当前被订阅的发布者是否有超出限定数量的订阅者 超出则不继续绑定
        if (len > this.defaultMaxEventListNum) {
            console.warn('订阅失败！！！\n 最大订阅限制数为：' + this.defaultMaxEventListNum + '; 您已超出最大订阅数，无法继续订阅。');
        } else {
            currentEvent.push(cb);
        }
    }
}

// 发布
/**
 * 发布事件参数
 * @param {*} eventName 被订阅的发布者名称
 */
Event.prototype.emit = function (eventName) {

    // 将多于的参数存储在数组中，传递给需要通知的订阅者
    var args = Array.prototype.slice.call(arguments, 1);


    // 定义变量 存储当前被订阅者订阅的发布者
    let currentEvent = null;

    // 判断是否存在被订阅的发布者对象 不存在则创建空对象 存在则依据发布者名称 查找对应的订阅者
    if (!this.event_list) {
        // 创建空对象
        this.event_list = {};
    } else {
        // 查找对应的订阅者
        currentEvent = this.event_list[eventName];
    }

    // 如果不存在对应的订阅者 直接返回
    if (!currentEvent) {
        return false;
    }
    // 如果发布者有且只有一个订阅者  直接指定订阅者对应需要执行的方法
    else if (isFunction(currentEvent)) {
        currentEvent.apply(this, args);
    }
    // 如果发布者对应多个订阅者 则遍历所有的订阅者 并且执行相应的方法
    else if (isArry(currentEvent)) {
        for (var i = 0; i < currentEvent.length; i++) {
            currentEvent[i].apply(this, args);
        }
    }
}

// 移除订阅
/**
 * 移除订阅参数
 * @param {*} eventName 被订阅的发布者名称
 * @param {*} cb 订阅者需要执行的方法
 */
Event.prototype.removeListener = function (eventName, cb) {

    // 定义变量 存储当前被订阅者订阅的发布者
    let currentEvent = null,
        index = 0;

    // 判断是否存在被订阅的发布者对象 不存在直接返回当前对象 如果存在则获取当前发布者对应的订阅者
    if (!this.event_list) {
        return this;
    } else {
        currentEvent = this.event_list[eventName];
    }
    // 如果不存在对应的订阅者 直接返回当前的对象
    if (!currentEvent) {
        return this;
    }
    // 如果有且只有一个订阅者 并且 方法相同 则移除该订阅者
    else if (isFunction(currentEvent)) {
        if (currentEvent == cb) {
            delete this.event_list[eventName];
        }
    }
    // 如果有且有多个订阅者 遍历所有的订阅者 将匹配方法相同的订阅者删除 
    else if (isArry(currentEvent)) {
        for (var i = 0; i < currentEvent.length; i++) {
            if (currentEvent[i] == cb) {
                this.event_list[eventName].splice(i - index, 1);
                if (this.event_list[eventName].length == 0) {
                    delete this.event_list[eventName]
                }
                index++;
            }
        }
    }
}


// 移除全部订阅
/**
 * 移除全部或指定事件的所有订阅
 * @param {*} eventName 被订阅的发布者名称
 * @returns 
 */
Event.prototype.removeAllListener = function (eventName) {

    // 定义变量 存储当前被订阅者订阅的发布者
    let currentEvent = null;

    // 如果不存在发布者对象数据 直接返回当前对象
    if (!this.event_list) {
        return this;
    }

    // 获取当前发布者对应的订阅者信息
    currentEvent = this.event_list[eventName];

    // 如果存在发布者 并且 参数不为空 则遍历当前的发布者对象数据  删除用户指定的发布者
    if (!eventName && arguments.length == 0) {
        let keys = Object.keys(this.event_list);
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            delete this.event_list[key];
        }
    }

    // 如果发布者对应的订阅者信息不为空 并且有且只有一个或者有且有多个订阅者 则删除发布者对象中的发布者
    if (currentEvent || isFunction(currentEvent) || isArry(currentEvent)) {
        delete this.event_list[eventName];
    } else {
        return this;
    }
}

// 订阅一次
/**
 * 订阅一次的参数
 * @param {*} eventName 被订阅者订阅的发布者名称
 * @param {*} cb 
 */
Event.prototype.onOnce = function (eventName, cb) {
    //实现原理与订阅的方法相同 实现原理在于 执行对应的订阅者需要执行的方法后 删除该发布者
    let _this = this;

    function callback() {
        _this.removeListener(eventName, callback);
        cb.apply(_this, arguments);
    }
    _this.on(eventName, callback)
}

// 判断是否为方法 用于判断发布者是否有且只有一个订阅者
function isFunction(fn) {
    return (fn instanceof Function);
}
// 判断是否为数组 用于判断发布者有且是否有多个订阅者
function isArry(arr) {
    return (arr instanceof Array);
}